;**** A P P L I C A T I O N   N O T E   A V R 3 0 4 ************************
;*
;* Title:		Half Duplex Interrupt Driven Software UART 
;* Version:		1.0
;* Last updated:	97.07.18
;* Target:		AT90Sxxxx (All AVR Devices)
;*
;* Support E-mail:	avr@atmel.com
;*
;* Code Size		:72 words
;* Low Register Usage	:2
;* High Register Usage	:5
;* Interrupt Usage	:External Interrupt,
;*			 Timer/Counter0 overflow interrupt
;*
;* DESCRIPTION
;*
;* This application note describes how to make a half duplex software UART
;* on any AVR device with the 8-bit Timer/Counter0 and External Interrupt.
;* As a lot of control applications communicate in one direction at a time
;* only, a half duplex UART will limit the usage of MCU resources.
;*
;* The constants N and R determine the data rate. R selects clock frequency
;* as described in the T/C Prescaler in the AVR databook. If the T/C pre-
;* scaling factor is denoted C, the following expression yields the data rate:
;*
;*		 XTAL
;*	 BAUD = ------		min. N*C = 17
;*		 N*C		max. N   = 170
;*
;* Absolute minimum value for N*C is 17 (which causes the interrupt flag to be 
;* set again before the interrupt is finished). Absolute maximum is 170.
;* (Caused by the 1.5bit-lenght that is necessary to receive bits correctly.)
;*
;* The UART uses PD2 as receive pin because it utilizes the external interrupt.
;* The transmit-pin is PD4 in this example, but it can be any other pins.
;*
;* Since the UART is half duplex, it can either send or recieve data. It can't
;* do both simoutaneausly. When idle it will automatically recieve incoming
;* data, but if it is transmitting data while incoming data arrives, it will
;* ignore it. Also, if u_transmit is called without waiting for the 'READY' bit
;* in the 'u_status' register to become cleared, it will abort any pending 
;* reception or transmittal.
;*
;*
;* *** Initialization
;*
;*  1. Call uart_init
;*  2. Enable global interrupts (with 'sei')
;*
;* *** Receive
;*
;*  1. Wait until RDR in 'u_status' becomes set
;*  2. Read 'u_buffer'
;*
;* *** Transmit
;*
;* (0. Initialize the UART by executing uart_init and sei)
;*  1. Wait until READY in 'u_status' becomes clear
;*  2. Set 'u_buffer'
;*  3. Call 'u_transmit'
;*
;**************************************************************************

.include "1200def.inc"

;***** BAUD-rate settings

				;BAUD-RATES @1MHz XTAL AND R=1
;.equ	N=104			; 9600
;.equ	N=52			;19200 
.equ	N=26			;38400
.equ	C=1			;Divisor
.equ	R=1			;R=1 when C=1


;***** UART Global Registers

.def	u_buffer	=r14	;Serial buffer
.def	u_sr		=r15	;Status-register storage
.def	u_tmp		=r16	;Scratchregister
.def	u_bit_cnt	=r17	;Bit counter
.def	u_status	=r18	;Status buffer
.def	u_reload	=r19	;Reload-register (internal - do not use)
.def	u_transmit	=r20	;Data to transmit


;***** Bit positions in the Status-register

.equ	RDR=0			;Receive data ready bit
.equ	TD=6			;Transmitting data (internal - read-only)
.equ	BUSY=7			;Busy-flag (internal - read-only)


;**************************************************************************
;*
;*	PROGRAM START - EXECUTION STARTS HERE
;*
;**************************************************************************
	.cseg

	.org $0000
	rjmp	start			;Reset handler

	.org INT0addr
	rjmp	ext_int0		;External interrupt handler

	.org OVF0addr
	rjmp	tim0_ovf		;Timer0 overflow handler

	.org ACIaddr
	reti				;Analog comparator handler (Not Used)



;**************************************************************************
;*
;* EXT_INT0 - External Interrupt Routine 0
;*
;*
;* DESCRIPTION
;* This routine is executed when a negative edge on the incoming serial
;* signal is detected. It disables further external interrupts and enables
;* timer interrupts (bit-timer) because the UART must now receive the
;* incoming data. 
;*
;* This routine sets bits in the GIMSK, TIFR and TIMSK registers. In this
;* code when the bits are set, it overwrites all other bits. This is done
;* because of the lack of available cycles when it operates at low clock
;* rate and high baudrates.
;*
;*
;* Total number of words	: 12
;* Total number of cycles	: 15 (incl. reti)
;* Low register usage		: 1 (u_sr)
;* High register usage		: 4 (u_bit_cnt,u_tmp,u_status,u_reload)
;*
;**************************************************************************
ext_int0:
	in	u_sr,SREG		;Store Status Register

	ldi	u_status,1<<BUSY	;Set busy-flag (clear all others)

	ldi	u_tmp,(256-(N+N/2)+(29/C));Set timer reload-value (to 1.5
	out	TCNT0,u_tmp		;  bit len). 29 = time delay that
					;  have already been used in this
					;  interrupt plus the time
					;  that will be used by the time
					;  delay between timer interrupt request
					;  and the actual sampling of the first
					;  data bit.

	ldi	u_tmp,1<<TOIE0		;Set bit 1 in u_tmp
	out	TIFR,u_tmp		;   to clear T/C0 overflow flag
	out	TIMSK,u_tmp		;   and enable T/C0 overflow interrupt

	clr	u_bit_cnt		;Clear bit counter
	out	GIMSK,u_bit_cnt		;Disable external interrupt

	ldi	u_reload,(256-N+(8/C))	;Set reload-value (constant).

	out	SREG,u_sr		;Restore SREG
	reti


;**************************************************************************
;*
;* TIM0_OVF - Timer/Counter 0 Overflow Interrupt
;*
;*
;* DESCRIPTION
;* This routine coordinates the transmition and reception of bits. This 
;* routine is automatically executed at a rate equal to the baud-rate. When
;* transmitting, this routine shifts the bits and sends it. When receiving,
;* it samples the bit and shifts it into the buffer.
;*
;* The serial routines uses a status register (u_status): READ-ONLY.
;*		BUSY	This bit indicates whenever the UART is busy
;*		TD	Transmit Data. Set when the UART is transmitting
;*		RDR	Receive Data Ready. Set when new data has arrived
;*			and it is ready for reading.
;*
;* When the RDR flag is set, the (new) data can be read from u_buffer.
;*
;* This routine also sets bits in TIMSK and TIMSK. See ext_int0 description
;*
;* 
;* Total number of words	: 35
;* Total number of cycles	: min. 18, max. 28 - depending on if it is
;*				  receiving or transmitting, what bit is
;*				  pending, etc.
;* Low register usage		: 2 (u_sr,u_buffer)
;* High register usage		: 4 (u_bit_cnt,u_tmp,u_status,u_reload)
;* 
;**************************************************************************
tim0_ovf:
	in	u_sr,SREG		;Store statusregister

	out	TCNT0,u_reload		;Reload timer

	inc	u_bit_cnt		;Increment bit_counter
	sbrs	u_status,TD		;if transmit-bit set
	rjmp	tim0_receive		;    goto receive

	sbrc	u_bit_cnt,3		;if bit 3 in u_bit_cnt (>7) is set
	rjmp	tim0_stopb		;    jump to stop-bit-part

	sbrc	u_buffer,0		;if LSB in buffer is 1
	sbi	PORTD,PD4		;    Set transmit to 1
	sbrs	u_buffer,0		;if LSB in buffer is 0
	cbi	PORTD,PD4		;    Set transmit to 0
	lsr	u_buffer		;Shift buffer right
	
	out	SREG,u_sr		;Restore SREG
	reti

tim0_stopb:
	sbi	PORTD,PD4		;Generate stop-bit

	sbrs	u_bit_cnt,0		;if u_bit_cnt==8 (stop-bit)
	rjmp	tim0_ret		;      jump to exit

tim0_complete:
	ldi	u_tmp,1<<INT0		;(u_bit_cnt==9):
	out	GIMSK,u_tmp		;Enable external interrupt
	clr	u_tmp 
	out	TIMSK,u_tmp		    ;Disable timer interrupt
	cbr	u_status,(1<<BUSY)|(1<<TD)  ;Clear busy-flag and transmit-flag

tim0_ret:
	out	SREG,u_sr		;Restore status register
	reti

tim0_receive:
	sec				;Set carry
	sbis	PIND,2			;if PD2=LOW         <=== SAMPLE HERE
	clc				;    clear carry
	ror	u_buffer		;Shift carry into data
	in	u_tmp,SREG		;Store SREG

	cpi	u_bit_cnt,9		;if u_bit_cnt!=9 (must sample stop-bit)
	brne	tim0_ret		;   exit interrupt

	out	SREG,u_tmp		;Get old SREG
	rol	u_buffer		;Rotate back data (to get rid of the stop-bit)

	sbr	u_status,1<<RDR		;Clear busy-flag
	rjmp	tim0_complete



;**************************************************************************
;*
;* uart_init - Subroutine for UART initialization
;*
;*
;* DESCRIPTION
;* This routine initializes the UART. It sets the timer and enables the
;* external interrupt (receiving). To enable the UART the global interrupt
;* flag must be set (with SEI).
;*
;*
;* Total number of words	: 8
;* Total number of cycles	: 11 (including the RET instructions)
;* Low register usage		: None
;* High register usage		: 2 (u_tmp,u_status)
;* 
;**************************************************************************
uart_init:	
	ldi	u_tmp,R
	out	TCCR0,u_tmp		;Start timer and set clock source
	ldi	u_tmp,1<<INT0
	out	GIMSK,u_tmp		;Enable external interrupt 0
	in	u_tmp,1<<ISC01
	out	MCUCR,u_tmp		;On falling edges
	clr	u_status		;Erase status-byte
	ret



;**************************************************************************
;*
;* uart_transmit - Subroutine for UART transmittal
;*
;*
;* DESCRIPTION
;* This routine initialize the UART to transmit data. The data to be sent
;* must be located in u_transmit. 
;*
;* Warning: This routine cancels all other transmittions or receptions.
;* So be careful. If a byte is being received and the timer interrupt is
;* currently running, the tranmittion may be corrupted. By checking the
;* READY-bit and/or TD-bit in the u_status register for safe transmissions.
;*
;* This routine also sets bits in TIMSK and TIMSK. See ext_int0 description
;*
;*
;* Total number of words	: 13
;* Total number of cycles	: 17 (including RET)
;* Low register usage		: 1 (u_buffer)
;* High register usage		: 4 (u_bit_cnt,u_tmp,u_transmit,u_reload)
;* 
;**************************************************************************
uart_transmit:
	ldi	u_status,(1<<BUSY)|(1<<TD)   ;Set only busy- and transmit flag

	clr	u_tmp
	out	GIMSK,u_tmp		;Disable external interrupt

	ser	u_bit_cnt		;Erase bit-counter (set all bits)
	mov	u_buffer,u_transmit	;Copy transmit-data to buffer

	ldi	u_tmp,1<<TOIE0		;Set bit 1 in u_tmp
	out	TIFR,u_tmp		;  to clear T/C0 overflow flag
	out	TIMSK,u_tmp		;  and enable T/C0 overflow interrupt
	
	ldi	u_reload,(256-N+(8/C))	;Set reload-value
	ldi	u_tmp,(256-N+(14/C))	;Set timer delay to first bit
	out	TCNT0,u_tmp		;Set timer reload-value (1 bit)

	cbi	PORTD,PD4		;Clear output (start-bit)
	ret



;**************************************************************************
;*
;*	Test/Example Program
;*
;* This example program can be used for evaluation of the UART agains a PC
;* running a terminal emulator. If a key is pressed on the PC keyboard, the
;* message 'You typed <characher>' is sent back. The character is also
;* presented on port B.
;*
;**************************************************************************

.def	tmp=r21				;Temp. register
.def	buffer=r22			;Recieved byte
.def	adr=r23				;EEPROM Address

start:	ser	tmp			;Initialize
	out	PORTD,tmp		;Set port D as input with pullups
	sbi	DDRD,DDD4		;    except PD4 -> output with 1's
	out	DDRB,tmp		;Set port B as output with 1's (LED-off)
	out	PORTB,tmp

	rcall	uart_init		;Init UART
	sei				;Enable interrupts

idle:	sbrs	u_status,RDR		;Wait for Character
	rjmp	idle
	mov	buffer,u_buffer		;Get recieved character
	out	PORTB,u_buffer		;Output the byte on port B
	ldi	adr,example_data	;Set/Restore pointer to EEPROM data

loop:	out	EEAR,adr		;Set the EEPROM's address
	sbi	EECR,EERE		;Send the Read strobe
	in	u_transmit,EEDR		;Put the data in the transmit register
	rcall	uart_transmit		;And transmit the data

wait:	sbrc	u_status,TD		;Wait until data is sent
	rjmp	wait

	inc	adr			;Increase pointer
	cpi	adr,example_data+12	;Reached byte 12? (End?)
	breq	idle			;    Yes, wait for new char.

	cpi	adr,example_data+10	;Reached byte 10?
	brne	loop			;    No, jump back

	mov	u_transmit,buffer	;Put data in transmit register
	rcall	uart_transmit		;And transmit it

wait2:	sbrc	u_status,TD		;Wait until data is sent
	rjmp	wait2

	rjmp	loop			;Continue sendig chars


;**************************************************************************
;*
;*	Test/Example program data.
;*
;* This is the data that will be sent back when a character is recieved.
;*
;**************************************************************************
	.eseg
example_data:
	.db	89	;'Y'
	.db	111	;'o'
	.db	117	;'u'
	.db	32	;' '
	.db	116	;'t'
	.db	121	;'y'
	.db	112	;'p'
	.db	101	;'e'
	.db	100	;'d'
	.db	32	;' '
	.db	13	;<CR>
	.db	10	;<LF>
